/*
 * Copyright (C) 2006,2008 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.catonine.swing.tail;

import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;

import javax.swing.text.Style;
import javax.swing.text.StyleConstants;

import nu.zoom.catonine.stylerule.StyleRule;
import nu.zoom.catonine.stylerule.StyleRules;

/**
 * Styled document that listens for new lines to add and colors the lines
 * according to the selected style rules.
 * 
 * @author "Johan Maasing" &lt;johan@zoom.nu&gt;
 */
public abstract class StyleRulesDocument extends ParagraphStyleDocument {

	private static final long serialVersionUID = 1L;
	final static String DEFAULT_CHARACTER_STYLE_NAME = "defaultCharacterStyleName";
	private final String[] availableFontFamilyNames;
	private StyleRules configuration = null;
	private Font defaultFont = null;
	private ConcurrentHashMap<String, StyleSpecification> styleguuidToSpecificationMap =
			new ConcurrentHashMap<String, StyleRulesDocument.StyleSpecification>();
	private final StyleSpecification SUPRESS_SPECIFICATION;
	private final StyleSpecification DEFAULT_SPECIFICATION;

	public StyleRulesDocument() {
		super();
		availableFontFamilyNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
		addStyle(DEFAULT_CHARACTER_STYLE_NAME, getStyle("default"));
		SUPRESS_SPECIFICATION = new StyleSpecification();
		SUPRESS_SPECIFICATION.supress = true;
		DEFAULT_SPECIFICATION = new StyleSpecification();
		DEFAULT_SPECIFICATION.styleName = DEFAULT_CHARACTER_STYLE_NAME;
	}

	protected void setConfiguration(StyleRules configuration) {
		this.configuration = configuration;
		setupStyles();
	}

	/**
	 * Get the styled document style name that should be used for the given log
	 * block. Matches the given string against the style rules to determine
	 * which style to use.
	 * 
	 * @param logblock
	 *            The string that should be styled.
	 * @return The style. Null if the block should be supressed, i.e. not drawn
	 *         at all.
	 */
	protected StyleSpecification getStyleSpecification(String logblock) {
		StyleRule match = null;
		if (configuration != null) {
			Iterator<StyleRule> patternIterator = configuration.getPatternStyles().iterator();
			while ((match == null) && patternIterator.hasNext()) {
				StyleRule config = patternIterator.next();
				if (!config.isDisabled()) {
					Matcher matcher = config.getPattern().matcher(logblock);
					if (matcher.matches()) {
						match = config;
					}
				}
			}
		}
		if (match != null) {
			if (!match.isSupress()) {
				StyleSpecification styleSpecification = styleguuidToSpecificationMap.get(match.getGuid());
				if (styleSpecification == null) {
					styleSpecification = new StyleSpecification();
					styleSpecification.styleName = match.getGuid();
					styleSpecification.fold = match.isFold();
					styleguuidToSpecificationMap.put(match.getGuid(), styleSpecification);
				}
				return styleSpecification;
			} else {
				return SUPRESS_SPECIFICATION;
			}
		} else {
			return DEFAULT_SPECIFICATION;
		}
	}

	@Override
	protected synchronized void setupStyles() {
		Enumeration<?> styleNameEnumeration = getStyleNames();
		while (styleNameEnumeration.hasMoreElements()) {
			final String styleName = (String) styleNameEnumeration.nextElement();
			removeStyle(styleName);
		}
		super.setupStyles();
		final Style defaultStyle = createDefaultStyle();

		if (configuration != null) {
			List<StyleRule> configurations = configuration.getPatternStyles();
			if (configurations != null) {
				for (StyleRule styleRule : configurations) {
					Style configStyle = addStyle(styleRule.getGuid(), defaultStyle);
					final Font font = styleRule.getFont();

					if (styleRule.getBackground() != null) {
						StyleConstants.setBackground(configStyle, styleRule.getBackground());
					}
					if (styleRule.getForeground() != null) {
						StyleConstants.setForeground(configStyle, styleRule.getForeground());
					}
					if (font != null) {
						StyleConstants.setBold(configStyle, font.isBold());
						StyleConstants.setItalic(configStyle, font.isItalic());

						String fontfamily = font.getFamily();
						for (String availableFontFamily : this.availableFontFamilyNames) {
							if (availableFontFamily.equalsIgnoreCase(fontfamily)) {
								StyleConstants.setFontFamily(configStyle, availableFontFamily);
							}
						}
						int fontSize = font.getSize();
						if (fontSize < 1) {
							// Backwards compat for styles that do not have
							// fontSize
							fontSize = 12;
						}
						// Too small fonts makes no sense, probably an error in
						// the file.
						fontSize = Math.max(6, fontSize);
						StyleConstants.setFontSize(configStyle, fontSize);
					}
				}
			}
		}
	}

	protected Style createDefaultStyle() {
		final Style defaultStyle = addStyle(DEFAULT_CHARACTER_STYLE_NAME, null);
		if (this.defaultFont == null) {
			this.defaultFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
		}
		StyleConstants.setFontFamily(defaultStyle, this.defaultFont.getFamily());
		StyleConstants.setFontSize(defaultStyle, this.defaultFont.getSize());
		StyleConstants.setBold(defaultStyle, this.defaultFont.isBold());
		StyleConstants.setItalic(defaultStyle, this.defaultFont.isItalic());
		return defaultStyle;
	}

	public Font getDefaultFont() {
		return this.defaultFont;
	}

	public void setDefaultFont(Font font) {
		this.defaultFont = font;
		setupStyles();
	}

	protected static class StyleSpecification {
		boolean fold = false;
		boolean supress = false;
		String styleName;
	}
}
